#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include <elasticlient/client.h>
#include <cpr/cpr.h>
#include <json/json.h>
#include "logger.h"

// 序列化
bool serialize(const Json::Value &val, std::string &str)
{
    Json::StreamWriterBuilder swb;
    swb.settings_["emitUTF8"] = true;
    std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
    std::stringstream ss;
    int res = sw->write(val, &ss);
    if (res != 0)
    {
        LOG_ERROR("json序列化失败");
        return false;
    }
    str = ss.str();
    return true;
}

// 反序列化
bool unserialize(const std::string &str, Json::Value &val)
{
    Json::CharReaderBuilder crb;
    std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
    std::string err;
    bool res = cr->parse(str.c_str(), str.c_str() + str.size(), &val, &err);
    if (res == false)
    {
        LOG_ERROR("json反序列化失败: {}", err);
        return false;
    }
    return true;
}

// 所有的ES操作都不创建ES客户端, 都由外部传入

// 创建索引类
class ESIndex
{
public:
    ESIndex(std::shared_ptr<elasticlient::Client> &cli, const std::string &name, const std::string &type = "_doc")
        : _index_name(name), _type(type), _cli(cli)
    {
        Json::Value settings;
        Json::Value analysis;
        Json::Value analyzer;
        Json::Value ik;

        ik["tokenizer"] = "ik_max_word"; // 设置最大分词
        analyzer["ik"] = ik;             // 使用ik中文分词器
        analysis["analyzer"] = analyzer;
        settings["analysis"] = analysis;
        _index["settings"] = settings;
    }

    ESIndex &append(const std::string &key, const std::string &type = "text", const std::string &analyzer = "ik_max_word", bool enabled = true)
    {
        Json::Value fields;
        fields["type"] = type;
        fields["analyzer"] = analyzer;
        fields["enabled"] = enabled;
        _properties[key] = fields;
        return *this;
    }

    bool create(const std::string &index_id = "default_index_id")
    {
        Json::Value mappings;
        mappings["dynamic"] = true;
        mappings["properties"] = _properties;
        _index["mappings"] = mappings;

        // 序列化请求
        std::string body;
        bool ret = serialize(_index, body);
        if (!ret)
        {
            LOG_ERROR("创建索引序列化失败");
            return false;
        }
        LOG_DEBUG("{}", body);
        // 发起创建索引请求
        try
        {
            auto resp = _cli->index(_index_name, _type, index_id, body);
            if (resp.status_code < 200 || resp.status_code >= 300) // 成功响应码在[200, 300)
            {
                LOG_ERROR("创建ES索引 {} 失败，响应状态码异常: {}", _index_name, resp.status_code);
                return false;
            }
        }
        catch (const std::exception &e)
        {
            // std::cerr << e.what() << '\n';
            LOG_ERROR("创建ES索引 {} 失败: {}", _index_name, e.what());
            return false;
        }
        return true;
    }

    ~ESIndex()
    {
    }

private:
    std::string _index_name;                    // 索引名称
    std::string _type;                          // 索引类型--默认为 "_doc"
    Json::Value _properties;                    // 索引属性Json序列
    Json::Value _index;                         // 创建索引的Json序列
    std::shared_ptr<elasticlient::Client> _cli; // es客户端
};

// 向索引新增数据类
class ESInsert
{
public:
    ESInsert(std::shared_ptr<elasticlient::Client> &cli, const std::string &name, const std::string &type = "_doc")
        : _index_name(name), _type(type), _cli(cli)
    {
    }

    template <typename T>
    ESInsert &append(const std::string &key, const T &val)
    {
        _items[key] = val;
        return *this;
    }

    bool insert(const std::string &id = "")
    {
        // 序列化插入数据
        std::string body;
        bool res = serialize(_items, body);
        if (!res)
        {
            LOG_ERROR("插入数据序列化失败");
            return false;
        }
        // 发起插入数据请求
        try
        {
            auto resp = _cli->index(_index_name, _type, id, body);
            if (resp.status_code < 200 || resp.status_code >= 300) // 成功响应码在[200, 300)
            {
                LOG_ERROR("新增数据 {} 失败，响应状态码异常: {}", body, resp.status_code);
                return false;
            }
        }
        catch (const std::exception &e)
        {
            LOG_ERROR("新增数据 {} 失败: {}", body, e.what());
            return false;
        }
        return true;
    }

    ~ESInsert()
    {
    }

private:
    std::string _index_name;                    // 索引名称
    std::string _type;                          // 索引类型--默认为 "_doc"
    Json::Value _items;                         // 向索引插入的数据的Json序列
    std::shared_ptr<elasticlient::Client> _cli; // es客户端
};

// 移除索引中数据类
class ESRemove
{
public:
    ESRemove(std::shared_ptr<elasticlient::Client> &cli, const std::string &name, const std::string &type = "_doc")
        : _index_name(name), _type(type), _cli(cli)
    {
    }

    bool remove(const std::string &id)
    {
        try
        {
            auto resp = _cli->remove(_index_name, _type, id);
            if (resp.status_code < 200 || resp.status_code >= 300)
            {
                LOG_ERROR("删除数据 {} 失败，响应状态码异常: {}", id, resp.status_code);
                return false;
            }
        }
        catch (std::exception &e)
        {
            LOG_ERROR("删除数据 {} 失败: {}", id, e.what());
            return false;
        }
        return true;
    }

    ~ESRemove()
    {
    }

private:
    std::string _index_name;                    // 索引名称
    std::string _type;                          // 索引类型--默认为 "_doc"
    std::shared_ptr<elasticlient::Client> _cli; // es客户端
};

// 在索引中搜索类
class ESSearch
{
public:
    ESSearch(std::shared_ptr<elasticlient::Client> &cli, const std::string &name, const std::string &type = "_doc")
        : _index_name(name), _type(type), _cli(cli)
    {
    }

    // 添加过滤部分规则 -- 精确过滤
    ESSearch &append_must_not_terms(const std::string &key, const std::vector<std::string> &vals)
    {
        Json::Value fields;
        for (auto val : vals)
        {
            fields[key].append(val);
        }
        Json::Value terms;
        terms["terms"] = fields;
        _must_not.append(terms);
        return *this;
    }

    // 添加应该遵循匹配规则 -- 模糊匹配(可以分词)
    ESSearch &append_should_match(const std::string &key, const std::string &val)
    {
        Json::Value field;
        field[key] = val;
        Json::Value match;
        match["match"] = field;
        _should.append(match);
        return *this;
    }

    // 添加必须匹配规则 -- 模糊匹配(可以分词)
    ESSearch &append_must_match(const std::string &key, const std::string &val)
    {
        Json::Value field;
        field[key] = val;
        Json::Value match;
        match["match"] = field;
        _must.append(match);
        return *this;
    }

    // 添加必须匹配规则 -- 精确匹配(不分词)
    ESSearch &append_must_term(const std::string &key, const std::string &val)
    {
        Json::Value field;
        field[key] = val;
        Json::Value term;
        term["term"] = field;
        _must.append(term);
        return *this;
    }

    // 进行搜索请求
    Json::Value search()
    {
        Json::Value cond;
        if (!_must_not.empty())
            cond["must_not"] = _must_not;
        if (!_should.empty())
            cond["should"] = _should;
        if (!_must.empty())
            cond["must"] = _must;

        Json::Value query;
        query["bool"] = cond;
        Json::Value root;
        root["query"] = query;

        // 将搜索请求序列化
        std::string body;
        bool res = serialize(root, body);
        if (!res)
        {
            LOG_ERROR("搜索数据序列化失败");
            return false;
        }

        // 发起搜索请求
        cpr::Response resp;
        try
        {
            resp = _cli->search(_index_name, _type, body);
            if (resp.status_code < 200 || resp.status_code >= 300)
            {
                LOG_ERROR("检索数据 {} 失败，响应状态码异常: {}", body, resp.status_code);
                return Json::Value();
            }
        }
        catch (const std::exception &e)
        {
            LOG_ERROR("检索数据 {} 失败: {}", body, e.what());
            return Json::Value();
        }

        // 将结果反序列化然后返回
        Json::Value json_res;
        res = unserialize(resp.text, json_res);
        if (!res)
        {
            LOG_ERROR("检索数据 {} 结果反序列化失败", resp.text);
            return Json::Value();
        }
        return json_res["hits"]["hits"];
    }

    ~ESSearch()
    {
    }

private:
    std::string _index_name;                    // 索引名称
    std::string _type;                          // 索引类型--默认为 "_doc"
    std::shared_ptr<elasticlient::Client> _cli; // es客户端

    Json::Value _must_not; // 必须过滤的匹配规则
    Json::Value _should;   // 应该遵守的匹配规则 -- 满足匹配条件其中之一即可
    Json::Value _must;     // 必须遵守的规则
};